/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package ca.usask.cs.SimulationModel;

import ca.usask.cs.decisiontree.*;
import ca.usask.cs.decisiontree.api.DecisionTree;
import ca.usask.cs.decisiontree.api.Edge;
import ca.usask.cs.decisiontree.api.IntermediateTreeNode;
import ca.usask.cs.decisiontree.api.TerminalNodeInterface;
import ca.usask.cs.decisiontree.api.Node;

import com.vensim.Vensim;


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Vector;


import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
/**
 *
 * @author tonyleung
 */
public class VensimSimulationController implements SimulationControllerInterface{
    // path name of where to store the vensim models documents. This needs to be changed depending on the machine
//    private final static String filePath = "./" ;
 //   private final static String filePath = "c:/tmp/" ;


    private ModelingAgentActionStackInterface vensimActionStack;
    private DecisionTree tree;
    private double decisionTreeStartTime;   // may be different than the model start time
    private String modelFileName;
    private Vensim venControl;
    private double curTime;
    private String simulationName;
    private int branchNumber;
    private int counter;
    
    private static Logger logger = Logger.getLogger("SimCision"); 

    /**
     * constructor, creates the VensimActionStack
     */
    public VensimSimulationController()
    {
    	PropertyConfigurator.configure("SimCision.config");
    	
    	logger.debug("In Vensim Controller Constructor");

    	this.vensimActionStack = new VensimActionStack();
        this.venControl= new Vensim("vendll32") ;  // ****TODO:  should be reset to vendll32

        logger.debug("Finished Vensim Controller Constructor");
        
    }

    /**
     * Sets up the simulation using the Decision Tree and set up the initial values
     * @param tree, startTime
     */
    public void init(DecisionTree theTree, String theModelFileName)
    {
        String loadModelCommand;
        this.tree = theTree;
        this.branchNumber = 0;

    	logger.debug("Initializing tree");
        
        modelFileName = theModelFileName;
        loadModelCommand =  "SPECIAL>LOADMODEL|"+modelFileName;

        if (true)
        	{
        	// ****comment out after testing
	        System.out.println("++++++++"+loadModelCommand+"+++++++");
	
	        this.venControl.command(loadModelCommand);
	        String varnames[] = this.venControl.get_varnames("*",0);
	
	        for(int i=0; i<varnames.length;i++) {
	            System.out.println("****"+varnames[i]+"****");
	        }
        	}

//        this.testMethod();
    	logger.debug("Finished initializing tree");
    }

    /**
     * retrieve the list of varnames in an iterator.7
     * @return
     */
    public Iterator<String> getVariableNames()
    {
        LinkedList<String> varNameList = new LinkedList<String>();
        String varnames[] = this.venControl.get_varnames("*",0);

        for(int i =0; i<varnames.length; i++)
        {
            varNameList.add(varnames[i]);
        }
        return varNameList.iterator();
    }
    /**
     * retrieve the list of varnames in an iterator.7
     * @return
     */
    public String[] getVariableNamesbyArray()
    {
       
        String varnames[] = this.venControl.get_varnames("*",0);       
        return varnames;
    }
    /**
     * retrieve the list of constant parameters in an iterator.7
     * @return
     */
    public Iterator<String> getConstantParameterNames()
    {
        LinkedList<String> varNameList = new LinkedList<String>();
        String varnames[] = this.venControl.get_varnames("*",Vensim.VARTYPE_CONSTANT);

        for(int i =0; i<varnames.length; i++)
        {
            varNameList.add(varnames[i]);
        }
        return varNameList.iterator();
    }

    /**
     * begin the simulation. Iterates the tree with dfs and use each of the edges to create an
     * ModelingAgentObject and insert it to the action Stack. When it reaches a Terminal Node
     * it calls runSimulation
     * 
     * For simplicity, we just assume that the start time is the start time of the model.  Extra choices will likely confuse the user.
     */
    public void startSimulation(String theSimulationName /*, double theDecisionTreeStartTime */)
    {
//        this.decisionTreeStartTime = theDecisionTreeStartTime; 
        this.simulationName = theSimulationName;
        Node curNode = this.tree.getRootNode();
        ModelingAgentObjectInterface rootObject = this.createModelingAgentObject(curNode.getVariableName(), Double.NEGATIVE_INFINITY, curNode.getTime());
        this.push(rootObject);
    //    setSeed();
        startSimulationRecurseHelper(curNode);
    }
    
    /**start to set up the random seed**/
    public void setSeed(){
		String str;
		InputStreamReader stdin = new InputStreamReader(System.in);
		BufferedReader bufin = new BufferedReader(stdin);
		try {
			System.out.print("please in put the seed");
			str = bufin.readLine();
			System.out.println("The seed you input in is " + str);
		} catch (IOException E) {
			System.out.println("the I/O errors has happend");
		}
    }

    /**
     * recursive function used to help startSimulation to recurse through the tree
     * using a dfs
     */
    protected void startSimulationRecurseHelper(Node theCurNode)
    {
        // if the curNode is a terminal node, run simulation
        if(theCurNode instanceof TerminalNode)
        {
            TerminalNodeInterface curNode = (TerminalNodeInterface) theCurNode;
//            ModelingAgentObjectInterface curModelingAgentObject = this.createModelingAgentObject(curNode.getVariableName(), Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY);
//            this.vensimActionStack.push(curModelingAgentObject);
            curNode.setRollBackValue(this.runSimulation(curNode.getVariableName()));
        }
        // if the curNode is not a terminal Node it must be an IntermediateTreeNode
        else
        {
            // cast to intermediate Node and grab its child edges
            IntermediateTreeNode curNode = (IntermediateTreeNode) theCurNode;
            Iterator<Edge> edgeList = curNode.getChildEdgeList();

            // for each child edge, create a modelingAgentObject from it, push it on the stack
            // and recurse. After recursion pop off the node to backtrack.
            while(edgeList.hasNext())
            {
                Edge curEdge = edgeList.next();
                ModelingAgentObjectInterface curModelingAgentObject = this.createModelingAgentObject(curEdge);
                this.vensimActionStack.push(curModelingAgentObject);
                this.startSimulationRecurseHelper(curEdge.getDestinationNode());
                this.pop();
            }
        }
    }

    /**
     * retrieves the iterator of the modelingAgentActionStack
     * @return
     */
    public Iterator<ModelingAgentObjectInterface> getModelingAgentActionStack()
    {
        return this.vensimActionStack.getList();
    }

    /**
     * creates a modelingAgentObjectInterface with the given edge
     * @param Edge
     * @return
     */
    public ModelingAgentObjectInterface createModelingAgentObject(Edge theEdge)
    {
        String varName;
        double value;
        double time;
        VensimObject venObject;

        varName = theEdge.getSourceNode().getVariableName();
        value = theEdge.getValue();
        time = theEdge.getDestinationNode().getTime();

        venObject = new VensimObject(varName, value, time);

        return venObject;
    }

    /**
     * creates a modelingAgentObjectInterface with the given edge
     * @param Edge
     * @return
     */
    protected ModelingAgentObjectInterface createModelingAgentObject(String varName, double value, double time)
    {
        VensimObject venObject = new VensimObject(varName, value, time);

        return venObject;
    }

    /**
     * pushes a ModelingAgentObject to the action Stack
     * @param modelingAgentObject
     */
    public void push(ModelingAgentObjectInterface modelingAgentObject)
    {
        this.vensimActionStack.push(modelingAgentObject);
    }

    /**
     * pop off a ModelingAgentObject from the action Stack
     * @return
     */
    public ModelingAgentObjectInterface pop()
    {
        return this.vensimActionStack.pop();
    }

    /**
     * Run the simulation with the current ActionStack
     * @return
     */
    protected double runSimulation(String terminalVarName)
    {
        double timeAtEndOfIntervalAfterSetting;
        double val;
        String varName = "";

        this.branchNumber++;
        String simName = this.simulationName + "_Branch_" + this.branchNumber;
    	logger.trace("In VensimSimulationController.runSimulation, initializing output run named " + simName);
        this.resetGame(this.getFinalTime(), simName);
        // retrieve the list of vensimActionStack
        ModelingAgentObjectInterface curModelAgentObject;
        Iterator<ModelingAgentObjectInterface> actionStackIter = this.vensimActionStack.getList();

        // iterates through the actionStack
        while(actionStackIter.hasNext())
        {
            // for each action, set the new game time interval and setVal
            // then call GameOn to run the simulation to that point.
            curModelAgentObject = actionStackIter.next();

            timeAtEndOfIntervalAfterSetting = curModelAgentObject.getTime();
            
            // obtain the current value of the time (the time at the beginning of interval) according to vensim
            double vensimTime = getVenVarValue("time")[0];
            double timePrecisionDemanded = 1E-5;		// we will tolerate small errors in time
            
            val = curModelAgentObject.getValue();
            varName = curModelAgentObject.getVariableName();

        	logger.trace("In VensimSimulationController.runSimulation, at " + vensimTime + ") setting variable " + varName + " to value " + val);
//            System.out.println("Simulating: TIME: " + time + " VAL: " + val + "VarName: " + varName);
            this.vensimSetGameAdvanceInterval(timeAtEndOfIntervalAfterSetting);
            this.vensimSetVal(varName, val);
            this.vensimAdvanceByGameIntervalViaGameON();
        }

        // return the value by calling get val
        float[] x = getVenVarValue(terminalVarName);

        double vensimTime = getVenVarValue("time")[0];
    	logger.trace("Final value for terminal node at time " + vensimTime + "=" + x[0]);
        this.vensimEndGame();
    	logger.trace("In VensimSimulationController.runSimulation, finished output run named " + simName);
        return x[0];
    }

    /**
     * Uses the vensim command to setVal for the specified varName and value
     * @param varName - variable name
     * @param value - value for the varName
     */
    protected boolean vensimSetVal(String varName, double value)
    {
        // if the values are null, that means it is the first node and nothing
        // needs to be set.
        if(varName == null || value == Double.NEGATIVE_INFINITY)
        {
           return true;
        }
        String venCommand = "SIMULATE>SETVAL|" + varName + " = " + value;
        int result = venControl.command( venCommand );
        if (result == 0)
        {
        	logger.error("Failed in VensimSimulationController.vensimSetVal, setting variable" + varName + " to value " + value);
            System.err.println(venCommand + " -- failed" );
            return false;
        }
        return true;
    }

    /**
     * Uses te vensim command to set the GameInterval for the next node
     * @param time
     */
    protected boolean vensimSetGameAdvanceInterval(double newTime)
    {
        double deltaTime = newTime - this.curTime;

    	logger.trace("In VensimSimulationController.vensimSetGameAdvanceInterval, attempting to advance by time interval " + deltaTime);

        String venCommand = "GAME>GAMEINTERVAL|" + deltaTime;
        int result = venControl.command( venCommand );


        // if error occured
        if (result == 0)
        {
            System.err.println(venCommand + " -- failed" );
            return false;
        }
        // if no error, set newTime as curTime
        else
        {
            this.curTime = newTime;
            return true;
        }
    }

    /**
     * Uses the vensim command GameOn to move to the next game interval
     */
    protected boolean vensimAdvanceByGameIntervalViaGameON()
    {
        counter++;
//        System.out.println("Counter : " + counter);
        int result = venControl.command("GAME>GAMEON");

        // if error occured
        if (result == 0)
        {
        	logger.error("Failed in VensimSimulationController.vensimAdvanceByGameIntervalViaGameON");
            System.err.println("GAME>GAMEON-- failed" );
            return false;
        }
        return true;
    }

    /**
     * Uses the vensim command GameOn to move to the next game interval
     */
    protected boolean vensimEndGame()
    {
        int result = venControl.command("GAME>ENDGAME");

        // if error occured
        if (result == 0)
        {
            System.err.println("GAME>ENDGAME-- failed" );
            return false;
        }
        return true;
    }

    /**
     * resets the game to allow to simulate a new actionStack
     * this needs to be refactor... it should reset tree without reloading a model
     */
    protected void resetGame(double finalTime, String simName)
    {
        String loadModelCommand =  "SPECIAL>LOADMODEL|"+modelFileName;
        venControl.command(loadModelCommand);
        venControl.command("SIMULATE>SETVAL|FINAL TIME = "+ finalTime);
//        venControl.command("SIMULATE>SETVAL|initial population = 500");
//        venControl.command("SIMULATE>SETVAL|intial carrying capacity = 500");
//        venControl.command("SIMULATE>SETVAL|average lifetime = 9");
//        venControl.command("SIMULATE>SETVAL|birth rate initial = 0.23");
        venControl.command("SIMULATE>RUNNAME|"+ simName);
        venControl.command("MENU>GAME");

	    // set the curtime as the start Time of the MODEL, so the simulation to the initial node of the tree knows to advance this to the first node in the tree
        this.curTime = getModelStartTime();
        //        venControl.command("SIMULATE>SETVAL|INITIAL TIME = "+ this.decisionTreeStartTime);
    }
    protected double getModelStartTime()
    {
        // get the start time of the model by reading a variable
	    float[] arrayModelStartTime = getVenVarValue("INITIAL TIME");
	    
	    return arrayModelStartTime[0];
    }
    // deprecated -- likely not required
    /*
    protected void advanceFromModelStartToDecisionTreeStartTime()
    {
	    // get the current initial time
	    float[] arrayModelStartTime = getVenVarValue("INITIAL TIME");
	    double modelStartTime = arrayModelStartTime[0];
	    
	    // figure out the difference between the model start time and the decision tree start time
	    double timeFromModelBeginUntilDecisionTreeStart = this.decisionTreeStartTime - modelStartTime;

	    // ok, now advance by this length of time
	    assert(timeFromModelBeginUntilDecisionTreeStart >= 0);

	    // if we have to advance by some non-zero time, do so 
	    if (timeFromModelBeginUntilDecisionTreeStart > 0)
	    {
		    this.vensimSetGameAdvanceInterval(timeFromModelBeginUntilDecisionTreeStart);
	        this.vensimAdvanceByGameIntervalViaGameON();
	    }

	    // set the curtime as the start Time of the decision tree
        this.curTime = this.decisionTreeStartTime;

    }
    */
    
    /**
     * retrieves the time variable for the last
     * @return
     */
    protected double getFinalTime()
    {
        return this.vensimActionStack.getFinalTime();
    }

    public float[] getVenVarValue(String varName){
        float[] val = new float[20];
        int x = venControl.get_val(varName,val);
        System.out.println("^^^Variable Value --"+varName+" : "+val[0]);
        return val;
    }


}
